fix: recover interrupted paykit sessions - #1339
Conversation
Regtest APKDownload bitkit-dev-debug universal APK (expires in 30 days). |
|
jvsena42
left a comment
There was a problem hiding this comment.
One medium finding, gated behind the Paykit UI flag, so it affects opted-in users on released builds and does not block. There is also a low one. Both are inline. The toast finding also applies to synonymdev/bitkit-ios#796 (AppScene toasts on every sessionRestorationFailed change).
Checked and clean:
- Billing reminders only post a notification; the tap path never pays. A retry only happens when nothing was posted, so no duplicate reminders. A worker that returns
retryis still an upcoming period, sosynchronize()keeps its work name rather than cancelling it. - Sign-out and wipe take
initializeMutex, so a queued retry then sees no identity. After a failed sign-out,_publicKeystays set and retry does nothing. - Ring cancel: before approval, cancelling ends the attempt without holding the lock. After approval, the new session is revoked and the keychain cleared, so retry finds nothing. During
Authenticating, retry is guarded. - Recovery cannot overwrite a live authorization:
_authState != Idle, andcompleteAuthenticationholds the mutex from approval through activation. - No nested
initializeMutexindeleteProfile,discardAbandonedSession,approveSignupAuthorrestoreSessionBackupState. - No secrets logged; new logs use
redacted(). - The republish clock-rollback check matches iOS.
- Signup alias stays disabled on an unreadable keychain (
getOrDefault(true)).
piotr-iohk
left a comment
There was a problem hiding this comment.
QA reviewed on 0d32249.
QA review
Reviewed the full PR diff against its merge base, at 0d32249.
No new actionable code findings.
Checked saved-session retry on reconnect and resume, silent restoration failures, cache isolation when the saved owner changes, signup suppression while a saved identity is unreadable, identity republish after a clock rollback, and billing reminders that run before the UTC period start. The earlier comments on cached identity data, repeated session-expired toasts, and wipe waiting on profile loading match the current source.
Unit and scheduler tests were inspected, not executed in this review. The author reports the local unit suite passed. CI on this commit succeeded, including the local end-to-end jobs. Those jobs do not drive device-clock or network-fault recovery. The manual procedure in journeys/paykit-clock-changes.md is the runtime check. Shared recovery, cache, and billing behavior was compared with bitkit-ios#796 (003030e); that was not a full review of the iOS pull request.
Device testing: not performed in this review.
Ready for device testing.
There was a problem hiding this comment.
Advice: ✅ Approve
Review: diff 16 files.
Matches synonymdev/bitkit-ios#796.
Findings:
3 inline (non-blocking)
Audit:
Audited - no findings.
Coverage:
QA: waits for the other reviewers' approval, or @ovi-reviewer test
Reviewed by gpt-6-sol-xhigh via gh-pr-review-loop skill
Commands: @ovi-reviewer review · test · retest · audit (author or owner) · wrong <why> (owner)
jvsena42
left a comment
There was a problem hiding this comment.
553e27b: ovi-reviewer's three threads are fixed. The unlock and the flag reset are consecutive non-suspending statements, so finally cannot unlock twice. Both loaders drop stale results via _publicKey, and the new wipe-during-loads test covers the main race. One low-severity follow-up inline.
piotr-iohk
left a comment
There was a problem hiding this comment.
QA reviewed on 553e27b.
QA review
1 actionable finding — resolve or provide an evidence-backed rebuttal.
Reviewed the full PR diff against its merge base bde47ec, at 553e27b.
Checked saved-session retry on reconnect and foreground, silent restoration retries, cache isolation when the saved owner changes, identity republish after a clock rollback, and the billing-worker time gate. Shared recovery, cache, republish, and billing behavior was compared with bitkit-ios#796 (003030e).
1 actionable finding, same mechanism as the open thread on superseded profile and contact loads.
Unit and scheduler tests were inspected, not executed. The author reports the local unit suite passed. CI, lint, and local end-to-end succeeded on this commit. Those jobs do not drive device-clock or network-fault recovery. The manual procedure in journeys/paykit-clock-changes.md is the runtime check.
Device testing: not performed in this review.
Additional test cases
- Android: Authorize identity A and delay its profile and contact fetches. Sign out, then authorize identity B before those fetches finish. B's name and contacts should appear without opening Profile or Contacts again, and A's name, avatar, and contact labels should not.
Findings
- [LOW] Reload profile after a superseded fetch — inline at
app/src/main/java/to/bitkit/repositories/PubkyRepo.kt:381.
piotr-iohk
left a comment
There was a problem hiding this comment.
QA reviewed on 5600005.
QA review
No new actionable code findings.
Reviewed the full PR diff against its merge base bde47ec, at 56000054.
Checked saved-session retry on reconnect and resume, silent restoration after the startup failure is reported, cache isolation when the saved owner changes, signup suppression while a saved identity is unreadable, identity republish after a clock rollback, the billing-worker time gate, and profile/contact loads after an older fetch is still in flight. At this revision those loads wait for the in-flight fetch and then load the current identity. That matches the behavior requested on the open thread, and completed authentication loads new identity after previous loads finish in PubkyRepoTest.kt covers the handoff. Shared recovery, cache, republish, and billing behavior was compared with bitkit-ios#796 (003030e).
Unit and scheduler tests were inspected. This review did not execute them. Lint passed on this commit. The debug build was still running. Those jobs do not drive device-clock or network-fault recovery. The manual procedure in journeys/paykit-clock-changes.md is the runtime check.
Device testing: not performed in this review.
Additional test cases
- Android: Cold-start online with an expired or revoked Ring grant. The session-expired notice appears once. Background the app, reopen it, and reconnect the network. The notice stays absent, the cached name remains, and the profile header still opens Ring authorization for that same identity.
|
@ben-kaufman this conflicts with master since the Ring PRs merged. Can you resolve it? |
There was a problem hiding this comment.
Advice: ✅ Approve
Reaudit: no code change since 7a1573d, 0 files.
No new findings; the rest is in the review.
Dropped as declined: A legacy cache can appear under another Ring identity when both owners are unreadable; When A’s saved session cannot be restored and the SDK has no readable owner, this null check keeps A’s….
Pair PR synonymdev/bitkit-ios#796: not compared; the Android ownerless persistent-cache migration is enough to decide this change.
Reviewed by autopilot-verdict via gh-pr-review-loop skill
Commands: @ovi-reviewer review · test · retest · audit (author or owner) · wrong <why> (owner)
addressed - reaudit confirmed
jvsena42
left a comment
There was a problem hiding this comment.
Re-checked 5600005 and 7a1573d. One LOW left, as a reply on the existing lock() thread: createIdentity still runs the loads under initializeMutex, which now blocks behind an in-flight load. Paykit-gated; affects opted-in users on released builds once the flag is on.
Clean: the lock() + stale-pk re-check unlocks on mismatch before any suspension, so a cancelled waiter never holds the lock; no lock-order inversion (nothing holds the load mutexes while waiting on initializeMutex). The cache owner tag comes from the same SDK-derived key as _publicKey, and matches() normalizes the pubky prefix, so same-identity restores keep the cache. activateBootstrapResult resets on a confirmed identity change from either owner. Backup restore resets the store before signIn. Upgrade from v2.5.0 pubky.json: ownerPublicKey defaults to null with ignoreUnknownKeys, the first load tags it, and downgrade still decodes.
There was a problem hiding this comment.
Advice: ✅ Approve
Reaudit: diff 2 files.
No new findings; the rest is in the review.
Equivalent pair: synonymdev/bitkit-ios#796.
Reviewed by gpt-6-sol-xhigh via gh-pr-review-loop skill
Commands: @ovi-reviewer review · test · retest · audit (author or owner) · wrong <why> (owner)
piotr-iohk
left a comment
There was a problem hiding this comment.
QA review
Scope: Full reassessment of the complete PR diff against merge base 3bbcb93, at 77239d2, after the comparison base moved on from the earlier review of 56000054.
No new actionable code findings.
Saved-session retry on reconnect and resume keeps the cached profile, reports the session-expired toast only for the startup failure, and stays behind an in-progress Ring adoption or wipe. Signup stays disabled while a saved or unreadable identity exists. A confirmed identity change clears the cached name and contact overrides. An ownerless legacy cache remains when the SDK owner is also unreadable, matching the resolved discussion on PaykitSdkService.kt. Identity republish runs again after a backward clock jump, and the billing worker defers a reminder that starts before the billing instant. Profile and contact loads wait for an older in-flight fetch, then drop a stale result. createIdentity loads after releasing initializeMutex, which covers the remaining change request on 7a1573d.
Validation: inspected the updated unit tests and did not execute them in this pass. Build, lint, and detekt passed on this commit. The local Appium shards do not drive device-clock or connectivity fault injection; that coverage stays on the manual procedure already in the PR. The iOS counterpart was outside this pass.
Device testing: not performed in this review.
Ready for device testing.
There was a problem hiding this comment.
Advice: ✅ Approve
Reaudit: diff 2 files.
No new findings; see the review.
Matching PR: synonymdev/bitkit-ios#796.
QA:
Test 1
Test 2
Test 3
Test 4
Test 5
Test 6
Warning
Tests 1–6 remain unverified because their network fault, device clock, or OS timezone behavior could not be tested.
Coverage:
Unit tests: 80% - New tests cover cache failure and wiping during a paused contact load; identity checks and the ViewModel caller were reviewed.
Reviewed by gpt-6-sol-xhigh via gh-pr-review-loop skill
Commands: @ovi-reviewer review · test · retest · audit (author or owner) · wrong <why> (owner)
A failed Paykit session restore after connection loss or a device clock change could make an existing profile appear missing and discard contacts. This PR preserves saved identity data, retries recovery when connectivity returns or the app resumes, and corrects retry and billing-reminder timing.
Fixes #1344.
Counterpart: iOS PR.
Related: #1334 also changes the persisted-identity lookup as part of backup protection; that overlapping hunk needs reconciling when both PRs merge.
Description
Out of Scope
Design
N/A — no UI changes.
Preview
Android offline-start/reconnect recordings were captured locally: the original build lost the profile name and did not recover; the fixed build retained the name and contact and restored the same identity about 1.7 seconds after reconnecting. Live clock-change recording remains pending.
QA Notes
Journeys
N/A — not drivable; see Manual Tests.
Manual Tests
Live grant-session clock-change E2E has not been rerun. Android offline cold-start/reconnect and contact preservation were verified on a disposable regtest emulator. The full procedure is in paykit-clock-changes.md.
Automated Checks
PaykitSdkServiceTest.kt— identity lookup failures stop activation without deleting saved state or credentials; activation tests cover same/different owners, normalized keys, legacy backup caches, and cache-reset failure.PubkyAuthHandlerRegistrarTest.ktandAppViewModelSendFlowTest.kt— saved/unreadable identities do not advertise signup; reconnect and foreground trigger recovery.PubkyRepoTest.kt— automatic retry after failure, unreadable credentials, queued wipe, active Ring authorization, and cancelled completion racing retry; restoration preserves profile data for retry; failed Ring auth cleans up only a session installed by that attempt, including unreadable ownership and cancellation coverage; identity switches discard stale in-memory profile/contact data and reset the contact-load marker.PubkyIdentityRepublishTest.kt— clock rollback retries publication and then resumes throttling.PaykitSubscriptionNotificationSchedulerTest.kt— a worker running before the billing boundary defers its reminder.PaykitSubscriptionTest.kt— timezone and DST changes preserve UTC billing boundaries.